package com.example.system.service.impl;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.starter.mybatis.util.PageUtil;
import com.example.starter.security.util.PasswordUtil;
import com.example.starter.security.util.SecurityUtil;
import com.example.starter.util.ExceptionUtil;
import com.example.system.convert.UserConvert;
import com.example.system.dto.UserDto;
import com.example.system.dto.UserInfo;
import com.example.system.dto.UserParam;
import com.example.system.dto.WeixinExtraDto;
import com.example.system.entity.Authority;
import com.example.system.entity.Role;
import com.example.system.entity.User;
import com.example.system.entity.WeixinExtra;
import com.example.system.mapper.UserMapper;
import com.example.system.mapper.WeixinExtraMapper;
import com.example.system.service.IAuthorityService;
import com.example.system.service.IRoleService;
import com.example.system.service.IUserService;
import com.example.system.vo.UserVo;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 *
 * @author 王令
 * @since 2022-08-05
 */
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    private final IRoleService roleService;
    private final IAuthorityService authorityService;
    private final WeixinExtraMapper weixinExtraMapper;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void create(UserDto userDto) {
        User user = UserConvert.INSTANCE.toUser(userDto);
        assertUniqueRecord(user);
        user.setPassword(PasswordUtil.encode(user.getPassword()));
        this.save(user);
        if (ObjUtil.isNotEmpty(userDto.getRolesId())) {
            baseMapper.addWithRoleJoins(user.getId(), userDto.getRolesId());
        }
        if (ObjUtil.isNotEmpty(userDto.getDeptsId())) {
            baseMapper.addWithDeptJoins(user.getId(), userDto.getDeptsId());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void update(UserDto userDto) {
        User user = UserConvert.INSTANCE.toUser(userDto);
        uidNotAllowModify(user);
        assertUniqueRecord(user);
        if (StrUtil.isNotBlank(user.getPassword())) {
            user.setPassword(PasswordUtil.encode(user.getPassword()));
        }
        this.updateById(user);
        baseMapper.removeWithRoleJoins(user.getId());
        baseMapper.removeWithDeptJoins(user.getId());
        if (ObjUtil.isNotEmpty(userDto.getRolesId())) {
            baseMapper.addWithRoleJoins(user.getId(), userDto.getRolesId());
        }
        if (ObjUtil.isNotEmpty(userDto.getDeptsId())) {
            baseMapper.addWithDeptJoins(user.getId(), userDto.getDeptsId());
        }
    }

    @Override
    public UserInfo findUserInfoByUser(User user) {
        if (ObjectUtil.isNull(user)) {
            return null;
        }
        UserInfo info = UserConvert.INSTANCE.toUserInfo(user);

        List<Role> roles = roleService.listByUserId(user.getId());
        List<Authority> authorities = new ArrayList<>();
        if (ObjectUtil.isNotEmpty(roles)) {
            info.setRoles(roles.stream().map(Role::getCode).collect(Collectors.toList()));
            authorities.addAll(authorityService.listPermissionByRolesId(
                    roles.stream().map(Role::getId).collect(Collectors.toList())));
        }
        info.setAuthorities(authorities.stream().map(Authority::getName).distinct().collect(Collectors.toList()));
        return info;
    }

    @Override
    public UserInfo findUserInfoByUsername(String username) {
        List<String> roles = roleService.listByUsername(username).stream().map(Role::getCode).collect(Collectors.toList());
        List<String> authorities = SecurityUtil.getAuthentication().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
        User user = this.getOne(Wrappers.<User>lambdaQuery()
                .select(User::getUsername,
                        User::getNickname,
                        User::getAvatar,
                        User::getGender,
                        User::getPhone,
                        User::getEmail,
                        User::getEnabled,
                        User::getNonLocked
                )
                .eq(User::getUsername, username));
        UserInfo userInfo = UserConvert.INSTANCE.toUserInfo(user);

        Optional.ofNullable(userInfo).ifPresent(info -> {
            info.setRoles(roles);
            info.setAuthorities(authorities);
        });
        return userInfo;
    }

    @Override
    public WeixinExtraDto findWeixinExtra(String username) {
        final WeixinExtra weixinExtra = weixinExtraMapper.selectOne(Wrappers.<WeixinExtra>lambdaQuery().eq(WeixinExtra::getUsername, username));
        return UserConvert.INSTANCE.toWeixinExtraDto(weixinExtra);
    }

    @Override
    public Page<UserVo> listWithPagination(Pageable pageable, UserParam param) {
        return baseMapper.listWithPagination(PageUtil.getPage(pageable), param);
    }

    @Override
    public UserVo findUserVoById(Long id) {
        return baseMapper.findUserVoById(id);
    }

    @Override
    public void saveWeixinExtra(WeixinExtra extra) {
        weixinExtraMapper.insert(extra);
    }

    private void uidNotAllowModify(User user) {
        User oldUser = baseMapper.selectById(user.getId());
        if (!StrUtil.equals(oldUser.getUsername(), user.getUsername())) {
            throw ExceptionUtil.wrap("用户名不能修改");
        }
    }

    private void assertUniqueRecord(User user) {
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
                .and(sql -> sql
                        .eq(User::getUsername, user.getUsername())
                        .or()
                        .eq(User::getPhone, user.getPhone())
                )
                .ne(user.getId() != null, User::getId, user.getId());

        User one = baseMapper.selectOne(wrapper);
        if (one != null) {
            if (StrUtil.equals(user.getUsername(), one.getUsername())) {
                throw ExceptionUtil.wrap("用户名已存在");
            } else if (StrUtil.equals(user.getPhone(), one.getPhone())) {
                throw ExceptionUtil.wrap("手机号已存在");
            } else {
                throw ExceptionUtil.wrap("记录已存在");
            }
        }
    }

}
